﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO.Compression;
using System.Text.RegularExpressions;
using System.Web;
using System.IO;

using FilmAlert.Common;

namespace FilmAlert.Imdb
{

    public class ImdbServices
    {
        private Encoding pageEncoding = Encoding.GetEncoding("ISO-8859-1");

        #region Constants

        const String TABLE_PATTERN = @"<table.*?>(.*?)</table>";
        const String YEAR_PATTERN =@"(\()(?<year>\d\d\d\d)(\))";
        const String TV_PATTERN = @"\(TV\)";
        const String TD_PATTERN = @"<td(.*?)>(.*?)</td>";
        const String IMDB_ID_REGEX = @"(?<=\w\w)\d\d\d\d\d\d\d";
        const String IMDB_TITLE_REGEX = @"<title>(?<filmTitle>.*?)</title>";
        const String HREF_PATTERN = @"<a.*?href=[""'](?<url>.*?)[""'].*?>(?<name>.*?)</a>";
        const String CHARACTER_PATTERN = @"<a\b[^>]*>(?<name>.*?)</a>";
        const String IMDB_PERSON_ID_REGEX = "(?<=nm).*";

        const String TR_PATTERN_1 = @"<tr\sclass="".*?"">(.*?)</tr>";
        const String TD_PATTERN_1 = @"<td\sclass=""nm"">(.*?)</td>";
        const String TD_PATTERN_2 = @"(?<=<td\sclass=""char"">)(.*?)(?=</td>)";



        #endregion

        #region Helpers

        private static Int32 GetImdbId(String text)
        {
            return Convert.ToInt32(Regex.Match(text, IMDB_ID_REGEX).ToString());
        }

        private static List<Film> GetFilmsByCategory(String imdbHtml, String category)
        {
            int index = imdbHtml.IndexOf(category);
            int startIndex = 0;

            String table = String.Empty;

            if (index > 0)
            {
                startIndex = imdbHtml.IndexOf("</p>", index);
                table = Regex.Match(imdbHtml.Substring(index, startIndex - index), TABLE_PATTERN).ToString();

                var listFilm = from Match M in Regex.Matches(table, TD_PATTERN)
                             let href = Regex.Match(M.ToString(), HREF_PATTERN) 
                             let year = Regex.Match(M.ToString(), YEAR_PATTERN)
                               let tvFilm = Regex.Match(M.ToString(), TV_PATTERN).Success
                             where year.Success
                               select (new Film(GetImdbId(href.Groups["url"].Value), href.Groups["name"].Value, Convert.ToInt16(year.Groups["year"].Value), tvFilm));

                try
                {
                    return listFilm.ToList();

                }
                catch (Exception)
                {
                }
            }
            return null;
        }

        private static Int32 GetPersonId(String text)
        {
            return Convert.ToInt32(Regex.Match(text, IMDB_PERSON_ID_REGEX).ToString().Replace("/", ""));
        }

        private static String GetCharacter(String text)
        {
            if (!text.StartsWith("<a"))
                return text;

            return Regex.Match(text, CHARACTER_PATTERN).Groups["name"].Value;
        }

        private Film GetFilmDetails(Int32 imdbId, String imdbHtml, Boolean allDetails)
        {
            String imdbTitle = Regex.Match(imdbHtml, IMDB_TITLE_REGEX).Groups["filmTitle"].Value;

            Film film = new Film(imdbId, imdbTitle);


            if (allDetails)
            {

                //Get cast
                var actors = from Match M in Regex.Matches(imdbHtml, TR_PATTERN_1)
                             let m1 = Regex.Match(Regex.Match(M.ToString(), TD_PATTERN_1).ToString(), HREF_PATTERN)
                             let m2 = Regex.Match(M.ToString(), TD_PATTERN_2).ToString()
                             select new Actor(GetPersonId(m1.Groups["url"].ToString()),
                                                HttpUtility.HtmlDecode(m1.Groups["name"].ToString()),
                                                HttpUtility.HtmlDecode(GetCharacter(m2.ToString())));

                film.Actors = actors.ToList();


                //Get directors
                Int32 startIndex =
                    imdbHtml.IndexOf("<h5>Director:</h5>") > 0 ? imdbHtml.IndexOf("<h5>Director:</h5>") : imdbHtml.IndexOf("<h5>Directors:</h5>");
                if (startIndex > 0)
                {
                    String directorsSection = imdbHtml.Substring(startIndex, imdbHtml.IndexOf("</div>", startIndex) - startIndex);

                    var directors = from Match M in Regex.Matches(directorsSection, HREF_PATTERN)
                                    where !M.Groups["url"].ToString().Contains("fullcredits#directors")
                                    select new Director(GetPersonId(M.Groups["url"].ToString()),
                                    HttpUtility.HtmlDecode(M.Groups["name"].ToString()));

                    film.Directors = directors.ToList();
                }
            }
            return film;
        }

        #endregion

        #region Public methods

        public Films SearchFilm(String filmTitle)
        {

            String url = String.Concat("http://www.imdb.com/find?s=tt&q=",
                HttpUtility.UrlEncode(filmTitle, pageEncoding));



            Films films = new Films();
            WebResponse webResponse;

            //Get content of the search page
            String imdbHtml = Helpers.GetHtml(url, out webResponse);


            //Check if we've been redirected straight to the movie page
            if (Regex.Match(webResponse.ResponseUri.ToString(), IMDB_ID_REGEX).Success)
            {
                films.ExactMatches.Add(GetFilmDetails(Convert.ToInt32(Regex.Match(webResponse.ResponseUri.ToString(), IMDB_ID_REGEX).ToString()), imdbHtml, false));
                return films;
            }

            films.PopularTitles = GetFilmsByCategory(imdbHtml, "<b>Popular Titles</b>");

            films.PartialMatches = GetFilmsByCategory(imdbHtml, "Titles (Partial Matches)");

            films.ApproxTitles = GetFilmsByCategory(imdbHtml, "Titles (Approx Matches)");

            films.ExactMatches = GetFilmsByCategory(imdbHtml, "Titles (Exact Matches)");


            return films;
        }

        public Film GetFilmDetails(Int32 imdbId)
        {
            String imdbIdString = "00000" + imdbId.ToString();
            String url = "http://www.imdb.com/title/tt" + (imdbIdString).Substring(imdbIdString.Length - 7, 7);

           return GetFilmDetails(imdbId, Helpers.GetHtml(url),true);
        }

        #endregion
    }
}
